package cn.lingyangwl.agile.tenant.core.utils;

import cn.lingyangwl.agile.common.core.exception.enums.GlobalErrorEnum;
import cn.lingyangwl.agile.model.constants.*;
import cn.lingyangwl.agile.model.module.tenant.TenantContext;
import cn.lingyangwl.agile.tenant.core.ITenantDataHandler;
import cn.lingyangwl.framework.core.utils.spring.SpringUtils;
import cn.lingyangwl.framework.tool.core.*;
import cn.lingyangwl.framework.tool.core.exception.BaseException;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;

/**
 * 多租户 Util
 *
 * @author shenguangyang
 */
@Slf4j
public class TenantUtils {

    /**
     * 使用指定租户，执行对应的逻辑
     *
     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户
     * 当然，执行完成后，还是会恢复回去
     *
     * @param tenantId 租户编号(子租户)
     * @param runnable 逻辑
     */
    public static void execute(String tenantId, Runnable runnable) {
        String oldTenantId = TenantContext.get().getTenantId();
        String oldMainTenantId = TenantContext.get().getMainTenantId();
        boolean oldFilterTenant = TenantContext.get().isFilterTenant();
        boolean oldFilterMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            TenantContext.get().setTenantId(tenantId);
            TenantContext.get().filterTenant(true);
            // 执行逻辑
            runnable.run();
        } finally {
            TenantContext.get().setTenantId(oldTenantId);
            TenantContext.get().setMainTenantId(oldMainTenantId);
            TenantContext.get().filterTenant(oldFilterTenant);
            TenantContext.get().filterMainTenant(oldFilterMainTenant);
        }
    }

    /**
     * 使用指定租户，执行对应的逻辑
     *
     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户
     * 当然，执行完成后，还是会恢复回去
     *
     * @param tenantId 租户编号(子租户)
     * @param callable 逻辑
     */
    public static <V> V execute(String tenantId, Callable<V> callable) {
        String oldTenantId = TenantContext.get().getTenantId();
        String oldMainTenantId = TenantContext.get().getMainTenantId();
        boolean oldFilterTenant = TenantContext.get().isFilterTenant();
        boolean oldFilterMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            TenantContext.get().setTenantId(tenantId);
            TenantContext.get().filterTenant(true);
            // 执行逻辑
            return callable.call();
        } catch (Exception e) {
            if (e instanceof BaseException) {
                throw (BaseException) e;
            }
            throw new RuntimeException(e);
        } finally {
            TenantContext.get().setTenantId(oldTenantId);
            TenantContext.get().setMainTenantId(oldMainTenantId);
            TenantContext.get().filterTenant(oldFilterTenant);
            TenantContext.get().filterMainTenant(oldFilterMainTenant);
        }
    }

    /**
     * 使用指定租户，执行对应的逻辑
     *
     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户
     * 当然，执行完成后，还是会恢复回去
     *
     * @param callable 逻辑
     */
    public static <V> V execute(DataIsolateLevel isolateLevel, Callable<V> callable) {
        String filterTenantId = getFilterTenantId(isolateLevel);
        return execute(isolateLevel, filterTenantId, callable);
    }

    /**
     * 使用指定租户，执行对应的逻辑
     *
     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户
     * 当然，执行完成后，还是会恢复回去
     *
     * @param runnable 逻辑
     */
    public static void execute(DataIsolateLevel isolateLevel, Runnable runnable) {
        String filterTenantId = getFilterTenantId(isolateLevel);
        execute(isolateLevel, filterTenantId, runnable);
    }


    /**
     * 使用指定租户，执行对应的逻辑
     *
     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户
     * 当然，执行完成后，还是会恢复回去
     *
     * @param tenantId 租户编号
     * @param callable 逻辑
     */
    public static <V> V execute(DataIsolateLevel isolateLevel, String tenantId, Callable<V> callable) {
        String oldTenantId = TenantContext.get().getTenantId();
        String oldMainTenantId = TenantContext.get().getMainTenantId();
        boolean oldFilterTenant = TenantContext.get().isFilterTenant();
        boolean oldFilterMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            configFilterTenantId(isolateLevel, tenantId);
            configFilterTenant(isolateLevel, false);
            // 执行逻辑
            return callable.call();
        } catch (Exception e) {
            if (e instanceof BaseException) {
                throw (BaseException) e;
            }
            log.error("error: ", e);
            throw new BizException(GlobalErrorEnum.FAIL);
        } finally {
            TenantContext.get().setTenantId(oldTenantId);
            TenantContext.get().setMainTenantId(oldMainTenantId);
            TenantContext.get().filterTenant(oldFilterTenant);
            TenantContext.get().filterMainTenant(oldFilterMainTenant);
        }
    }

    public static void execute(DataIsolateLevel isolateLevel, String tenantId, Runnable runnable) {
        execute(isolateLevel, tenantId, () -> {
            runnable.run();
            return null;
        });
    }

    /**
     * 忽略租户，执行对应的逻辑, 真对子租户
     *
     * @param runnable 逻辑
     */
    public static void executeIgnore(Runnable runnable) {
        boolean oldFilterTenant = TenantContext.get().isFilterTenant();
        boolean oldFilterMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            TenantContext.get().filterTenant(false);
            TenantContext.get().filterMainTenant(false);
            // 执行逻辑
            runnable.run();
        } finally {
            TenantContext.get().filterTenant(oldFilterTenant);
            TenantContext.get().filterMainTenant(oldFilterMainTenant);
        }
    }

    /**
     * 忽略租户，执行对应的逻辑, 真对子租户
     * @param isolateLevel 隔离级别
     * @param runnable 逻辑
     */
    public static void executeIgnore(DataIsolateLevel isolateLevel, Runnable runnable) {
        boolean oldTenant = TenantContext.get().isFilterTenant();
        boolean oldMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            configFilterTenant(isolateLevel, true);
            // 执行逻辑
            runnable.run();
        } finally {
            TenantContext.get().filterTenant(oldTenant);
            TenantContext.get().filterTenant(oldMainTenant);
        }
    }

    public static <T> T executeIgnore(DataIsolateLevel isolateLevel, Callable<T> callable) {
        boolean oldTenant = TenantContext.get().isFilterTenant();
        boolean oldMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            configFilterTenant(isolateLevel, true);
            // 执行逻辑
            return callable.call();
        } catch (Exception e) {
            if (e instanceof BaseException) {
                throw (BaseException) e;
            }
            log.error("error: ", e);
            throw new BizException(GlobalErrorEnum.FAIL);
        } finally {
            TenantContext.get().filterTenant(oldTenant);
            TenantContext.get().filterTenant(oldMainTenant);
        }
    }

    private static void configFilterTenant(DataIsolateLevel isolateLevel, boolean isIgnore) {
        if (isolateLevel == DataIsolateLevel.ALL) {
            TenantContext.get().filterTenant(!isIgnore);
            TenantContext.get().filterMainTenant(!isIgnore);
            return;
        }
        TenantContext.get().filterMainTenant(false);
        if (isolateLevel == DataIsolateLevel.ALONE || isolateLevel == DataIsolateLevel.MGT) {
            TenantContext.get().filterTenant(!isIgnore);
            return;
        }

        TenantContext.get().filterTenant(false);
        if (isolateLevel == DataIsolateLevel.MAIN) {
            TenantContext.get().filterMainTenant(!isIgnore);
        }
    }

    private static void configFilterTenantId(DataIsolateLevel isolateLevel, String tenantId) {
        if (isolateLevel == DataIsolateLevel.ALONE || isolateLevel == DataIsolateLevel.ALL) {
            TenantContext.get().setTenantId(tenantId);
        } else if (isolateLevel == DataIsolateLevel.MAIN) {
            TenantContext.get().setMainTenantId(tenantId);
        } else if (isolateLevel == DataIsolateLevel.MGT) {
            String mgtSceneTenantId = TenantContext.get().getMgtSceneTenantId();
            if (Objects.isNull(mgtSceneTenantId) || TenantContext.EMPTY_TENANT_ID.equals(mgtSceneTenantId)) {
                tenantId = SpringUtils.getBean(ITenantDataHandler.class).getMgtSceneTenantId(tenantId);
                TenantContext.get().setMgtSceneTenantId(tenantId);
            }
            TenantContext.get().setTenantId(tenantId);
        }
    }

    private static String getFilterTenantId(DataIsolateLevel isolateLevel) {
        if (isolateLevel == DataIsolateLevel.ALONE || isolateLevel == DataIsolateLevel.ALL) {
            return TenantContext.get().getRawTenantId();
        } else if (isolateLevel == DataIsolateLevel.MAIN) {
           return TenantContext.get().getMainTenantId();
        } else if (isolateLevel == DataIsolateLevel.MGT) {
            String mgtTenantId = TenantContext.get().getMgtSceneTenantId();
            if (StringUtils.isEmpty(mgtTenantId) || TenantContext.EMPTY_TENANT_ID.equals(mgtTenantId)) {
                mgtTenantId = SpringUtils.getBean(ITenantDataHandler.class).getMgtSceneTenantId(TenantContext.get().getTenantId());
                TenantContext.get().setMgtSceneTenantId(mgtTenantId);
            }
            return mgtTenantId;
        }
        return TenantContext.EMPTY_TENANT_ID;
    }

    /**
     * 针对子租户
     */
    public static <V> V executeIgnore(Callable<V> callable) {
        boolean oldFilterTenant = TenantContext.get().isFilterTenant();
        boolean oldFilterMainTenant = TenantContext.get().isFilterMainTenant();
        try {
            TenantContext.get().filterTenant(false);
            TenantContext.get().filterMainTenant(false);
            // 执行逻辑
            return callable.call();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            TenantContext.get().filterTenant(oldFilterTenant);
            TenantContext.get().filterTenant(oldFilterMainTenant);
        }
    }

    /**
     * 将多租户编号，添加到 header 中
     *
     * @param headers HTTP 请求 headers
     */
    public static void addTenantHeader(Map<String, String> headers) {
        String tenantId = TenantContext.get().getTenantId();
        if (StringUtils.isNotEmpty(tenantId)) {
            headers.put(RequestCons.Headers.TENANT_ID, tenantId);
        }
    }

}
